#pragma once

#include <string>
#include <iostream>
#include <unordered_map>
#include <vector>
#include <sstream>
#include <functional>
#include <memory>
#include <fstream>

#include "Session.hpp"

static const std::string base_sep = "\r\n";
static const std::string line_sep = ": ";
static const std::string space_sep = " ";
static const std::string httpversion = "HTTP/1.0";
static const std::string prefixpath = "wwwroot";
static const std::string homepage = "index.html";
static const std::string suffixsep = ".";
static const std::string html_404 = "404.html";
static const std::string arg_sep = "?";
static const std::string cookie_prefix = "Cookie: ";
static const std::string session_prefix = "sessionid=";

class HttpRequest
{
private:
    std::string GetLine(std::string &reqstr)
    {
        auto pos = reqstr.find(base_sep);
        if (pos == std::string::npos)
            return "";
        std::string line = reqstr.substr(0, pos);
        reqstr.erase(0, line.size() + base_sep.size());
        return line.empty() ? base_sep : line;
    }

    void ParseReqLine()
    {
        std::stringstream ss(_req_line);
        ss >> _req_method >> _Url >> _Version;
        // url: /index.html?a=1&b=2

        if (_req_method == "GET")
        {
            auto pos = _Url.find(arg_sep);
            if (pos != std::string::npos)
            {
                _body_text = _Url.substr(pos + arg_sep.size());
                _Url.resize(pos);
            }
        }

        _path += _Url;
        if (_path[_path.size() - 1] == '/')
        {
            _path += homepage;
        }

        auto pos = _path.rfind(suffixsep);
        if (pos == std::string::npos)
        {
            _suffix = ".default";
        }
        else
        {
            _suffix = _path.substr(pos);
        }
    }

    void ParseHeaders()
    {
        for (auto &header : _req_headers)
        {
            auto pos = header.find(line_sep);
            if (pos == std::string::npos)
                continue;
            std::string k = header.substr(0, pos);
            std::string v = header.substr(pos + line_sep.size());
            if (k.empty() || v.empty())
                continue;
            _req_headerkv.insert(std::make_pair(k, v));
        }
    }

public:
    std::string SessionId()
    {
        return _sessionid;
    }

    std::string Url()
    {
        return _Url;
    }

    HttpRequest() : _blank_line(base_sep), _path(prefixpath)
    {
    }

    std::string GetBodyText()
    {
        return _body_text;
    }

    void Deserialize(std::string &reqstr)
    {
        _req_line = GetLine(reqstr);
        std::string header;
        do
        {
            header = GetLine(reqstr);
            if (header.empty() || header == base_sep)
                break;
            _req_headers.push_back(header);
        } while (1);

        for (auto &line : _req_headers)
        {
            auto pos = line.find(cookie_prefix);
            if (pos == std::string::npos)
                continue;
            std::string buffer = line.substr(pos + cookie_prefix.size());
            _cookies.emplace_back(buffer);
        }

        for (auto &cookie : _cookies)
        {
            if (strncmp(session_prefix.c_str(), cookie.c_str(), session_prefix.size()) == 0)
            {
                _sessionid = cookie.substr(session_prefix.size());
            }
        }

        _body_text = reqstr;

        ParseReqLine();
        ParseHeaders();
    }

    void Print()
    {
        std::cout << "### " << _req_line << std::endl;
        // for (auto &header : _req_headers)
        //     std::cout << "@@@ " << header << std::endl;
        std::cout << "!!! " << _blank_line;
        std::cout << ">>> " << _Version << std::endl;

        std::cout << "$$$ Method: " << _req_method << "  Url: " << _Url << "  Version: " << _Version << std::endl;
        for (auto &[k, v] : _req_headerkv)
        {
            std::cout << "---> " << k << " -> " << v << std::endl;
        }

        std::cout << "_body_text" << _body_text << std::endl;
    }

    ~HttpRequest()
    {
    }

    std::string GetPath()
    {
        return _path;
    }

    std::string GetSuffix()
    {
        return _suffix;
    }

private:
    std::string _req_line;
    std::vector<std::string> _req_headers;
    std::string _blank_line;
    std::string _body_text;

    std::string _req_method;
    std::string _path;
    std::string _suffix;
    std::string _Url;
    std::string _Version;
    std::unordered_map<std::string, std::string> _req_headerkv;
    std::vector<std::string> _cookies;
    std::string _sessionid;
};

class HttpResponse
{
public:
    HttpResponse() : _blank_line(base_sep), _version(httpversion)
    {
    }
    void AddBodyText(const std::string &bodytext)
    {
        _body_text = bodytext;
    }
    void AddHeader(const std::string &k, const std::string &v)
    {
        _resp_headerkv.insert(std::make_pair(k, v));
    }
    void AddCode(int code, const std::string &status)
    {
        _status_code = code;
        _desc = status;
    }
    std::string Serialize()
    {
        // 1. _status_line
        _status_line = _version + space_sep + std::to_string(_status_code) + space_sep + _desc + base_sep;

        // 2. add headers
        for (auto &[k, v] : _resp_headerkv)
        {
            _resp_headers.emplace_back(k + line_sep + v + base_sep);
        }

        // return ret
        std::string ret = _status_line;
        for (auto &line : _resp_headers)
        {
            ret += line;
        }
        ret += _blank_line;
        ret += _body_text;

        return ret;
    }
    ~HttpResponse()
    {
    }

private:
    std::string _status_line;
    std::vector<std::string> _resp_headers;
    std::string _blank_line;
    std::string _body_text;

    std::string _version;
    int _status_code;
    std::string _desc;
    std::unordered_map<std::string, std::string> _resp_headerkv;
};

using func_t = std::function<HttpResponse(HttpRequest &)>;

class HttpServer
{
private:
    std::string GetFileContent(const std::string &path)
    {
        std::cout << "path: " << path << std::endl;
        std::ifstream in(path, std::ios::binary);
        if (!in.is_open())
            return "";
        in.seekg(0, in.end);
        int filesize = in.tellg();
        in.seekg(0, in.beg);

        std::string content;
        content.resize(filesize);
        in.read((char *)content.c_str(), filesize);
        in.close();
        std::cout << "content read success\n";
        return content;
    }

public:
    HttpServer()
    {
        _mime_type.insert(std::make_pair(".html", "text/html"));
        _mime_type.insert(std::make_pair(".jpg", "image/jpeg"));
        _mime_type.insert(std::make_pair(".png", "image/png"));
        _mime_type.insert(std::make_pair(".defualt", "text/html"));

        _code_to_desc.insert(std::make_pair(100, "Continue"));
        _code_to_desc.insert(std::make_pair(200, "OK"));
        _code_to_desc.insert(std::make_pair(201, "Created"));
        _code_to_desc.insert(std::make_pair(301, "Moved Permanently"));
        _code_to_desc.insert(std::make_pair(302, "Found"));
        _code_to_desc.insert(std::make_pair(404, "Not Found"));

        _month_name = {"Jan", "Fab", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
        _week_name = {"Sun", "Mon", "Tue", "Wed", "Fri", "Sat"};
    }

    /*
        std::string HttpHandlerServer(std::string &reqstr)
        {
    #ifdef TEST
            std::cout << "------------------------------------------------";
            std::cout << req;

            std::string responsester = "HTTP/1.1 200 OK\r\n";
            responsester += "Content-Type: text/html\r\n";
            responsester += "\r\n";
            responsester += "<html><h1>hello Linux, hello Bite!</h1></html>";

            return responsester;
    #else
            // std::cout<<reqstr<<std::endl;
            std::cout << "------------------------------------------------" << std::endl;
            HttpResponse resp;
            HttpRequest req;
            req.Deserialize(reqstr);
            req.Print();

            if (req.GetPath() == "wwwroot/redir")
            {
                std::string redir_path = "https://www.mihoyo.com";
                resp.AddCode(302, _code_to_desc[302]);
                resp.AddHeader("Location", redir_path);
            }
            else if(!req.GetBodyText().empty())
            {
                if(IsServerExists(req.GetPath()))
                {
                    resp = _server_list[req.GetPath()](req);
                }
            }
            else
            {
                std::string content = GetFileContent(req.GetPath());
                if (content.empty())
                {
                    content = GetFileContent("wwwroot/404.html");
                    resp.AddBodyText(content);
                    resp.AddCode(404, _code_to_desc[404]);
                    resp.AddHeader("Content-Length", std::to_string(content.size()));
                    resp.AddHeader("Content-Type", _mime_type[req.GetSuffix()]);
                }
                else
                {
                    resp.AddBodyText(content);
                    resp.AddCode(200, _code_to_desc[200]);
                    resp.AddHeader("Content-Length", std::to_string(content.size()));
                    resp.AddHeader("Content-Type", _mime_type[req.GetSuffix()]);
                }
            }

            return resp.Serialize();
    #endif
        }
    */

    std::string ExpireTimeUseRCF1123(int t)
    {
        time_t timeout = time(nullptr) + t;
        struct tm *tm = gmtime(&timeout);
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "%s, %02d %s %d %02d:%02d:%02d",
                 _month_name[tm->tm_mon].c_str(),
                 tm->tm_mday,
                 _week_name[tm->tm_wday].c_str(),
                 tm->tm_year + 1900,
                 tm->tm_hour,
                 tm->tm_min,
                 tm->tm_sec);
        return buffer;
    }

    std::string HandlerServer(std::string &reqstr)
    {
        std::cout<<reqstr<<std::endl;
        HttpResponse resp;
        HttpRequest req;
        req.Deserialize(reqstr);
        // req.Print();
        resp.AddCode(200, "OK");
        resp.AddBodyText("<html><h1>hello Linux</h1></html>");
        resp.AddHeader("Content-Type", _mime_type[".html"]);

        static int _cnt = 0;
        std::string sessionid = req.SessionId();
        if (req.Url() == "/login")
        {
            if (sessionid.empty())
            {
                std::string user = "User-" + std::to_string(_cnt++);
                session_ptr sp = std::make_shared<Session>(user, "ok");
                std::string userid = _session_manager.Insert(sp);
                LOG(INFO, "%s 注册成功！\n", user.c_str());
                resp.AddHeader("Set-Cookie", "sessionid=" + userid+"; expires="+ExpireTimeUseRCF1123(3600));
            }
        }
        else
        {
            session_ptr sp = _session_manager.GetSession(sessionid);
            if (sp == nullptr)
            {
                LOG(INFO, "Cookie: %s  已过期，需要清理\n", sessionid.c_str());
            }
            else
            {
                LOG(INFO, "%s 正在活跃\n", sp->GetName().c_str());
            }
        }

        // resp.AddHeader("Set-Cookie", "usename=zhangsan");  // Test: cookie
        // resp.AddHeader("Set-Cookie", "username=zhangsan; expires="+ExpireTimeUseRCF1123(60)); // Test: cookie expires time
        // resp.AddHeader("Set-Cookie", "username=zhangsan; path=/a/b"); // Test: cookie path

        std::string ret = resp.Serialize();
        return ret;
    }

    void InsertServerList(const std::string &servername, func_t f)
    {
        std::string s = prefixpath + servername;
        _server_list.insert(std::make_pair(s, f));
    }

    bool IsServerExists(const std::string &servername)
    {
        return _server_list.count(servername);
    }

    ~HttpServer()
    {
    }

private:
    std::unordered_map<std::string, std::string> _mime_type;
    std::unordered_map<int, std::string> _code_to_desc;
    std::unordered_map<std::string, func_t> _server_list;
    std::vector<std::string> _month_name;
    std::vector<std::string> _week_name;
    SessionManager _session_manager;
};